﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Text.RegularExpressions;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Threading.Tasks;
using System.Dynamic;


namespace GW2_Marketeer__WPF_
{
    public class Marketeer : INotifyPropertyChanged
    {
        private int m_nItemCount;
        private List<string> m_listItemNames;
        private Dictionary<string, GWItem> m_dictItems;
        private Dictionary<string, int> m_dictNumItemsToCraft;
        private Dictionary<string, int> m_dictNumItemsToBuy;
        private Dictionary<string, int> m_dictPriceTotalsByItem;
        private int m_nTotalCost;
        private int m_nTotalRevenue;
        private int m_nTotalProfit;
        private double m_dTotalExpectedROI;

        private static readonly Lazy<Marketeer> _instance = new Lazy<Marketeer>(() => new Marketeer());
        public static Marketeer Instance
        {
            get
            {
                return _instance.Value;
            }
        }

        private Marketeer()
        {
            ResetAllData();
        }

        private bool _loading;
        public bool Loading
        {
            get
            {
                return _loading;
            }
            set
            {
                _loading = value;
                NotifyPropertyChanged("Loading");
            }
        }

        private bool _ahPricesLoaded;
        public bool AHPricesLoaded
        {
            get
            {
                return _ahPricesLoaded;
            }
            set
            {
                _ahPricesLoaded = value;
                NotifyPropertyChanged("AHPricesLoaded");
            }
        }

        public int GetItemCount()
        {
            return m_nItemCount;
        }

        public List<string> GetListItemNames()
        {
            return m_listItemNames;
        }

        public GWItem GetItemByName(string sName)
        {
            return m_dictItems[sName];
        }

        public IEnumerable<GWItem> GetAllItems()
        {
            return m_dictItems.Values;
        }

        private ObservableCollection<GWItem> _gwItems;
        public ObservableCollection<GWItem> GWItems
        {
            get
            {
                return _gwItems ?? (_gwItems = new ObservableCollection<GWItem>(m_dictItems.Values));
            }
            private set
            {
                _gwItems = value;
                NotifyPropertyChanged("GWItems");
            }
        }

        private ObservableCollection<ShoppingItem> _shoppingList;
        public ObservableCollection<ShoppingItem> ShoppingList
        {
            get
            {
                
                if (_shoppingList != null)
                    return _shoppingList;

                _shoppingList = new ObservableCollection<ShoppingItem>();

                foreach (var item in m_dictNumItemsToBuy)
                {
                    if (item.Value > 0)
                        _shoppingList.Add(new ShoppingItem { Name = item.Key, Quantity = item.Value, AHPrice = m_dictItems[item.Key].AuctionHousePrice });
                }

                _shoppingList.Add(new ShoppingItem { Name = "Total", Quantity = _shoppingList.Select(x => x.Quantity).Sum(), TotalCost = _shoppingList.Select(x => x.TotalCost).Sum() });

                return _shoppingList;
            }
            private set
            {
                _shoppingList = value;
                NotifyPropertyChanged("ShoppingList");
            }
        }

        public Dictionary<string, int> GetShoppingListNumItemsToBuy()
        {
            return m_dictNumItemsToBuy;
        }

        public Dictionary<string, int> GetShoppingListPriceTotals()
        {
            return m_dictPriceTotalsByItem;
        }

        public int GetTotalCost()
        {
            return m_nTotalCost;
        }

        public int GetTotalRevenue()
        {
            return m_nTotalRevenue;
        }

        public int GetTotalProfit()
        {
            return m_nTotalProfit;
        }

        public double GetTotalExpectedROI()
        {
            return m_dTotalExpectedROI;
        }

        public void ResetAllData()
        {
            m_nItemCount = 0;
            m_listItemNames = new List<string>();
            m_dictItems = new Dictionary<string, GWItem>();
            m_dictNumItemsToCraft = new Dictionary<string, int>();
            m_dictNumItemsToBuy = new Dictionary<string, int>();
            m_dictPriceTotalsByItem = new Dictionary<string, int>();
            m_nTotalCost = 0;
            m_nTotalRevenue = 0;
            m_nTotalProfit = 0;
            m_dTotalExpectedROI = 0.0;
            GWItems = null;
            AHPricesLoaded = false;
            ShoppingList = null;
        }

        private void ResetAHAndCalculatedData()
        {
            foreach (KeyValuePair<string, GWItem> kvp in m_dictItems)
            {
                GWItem item = kvp.Value;
                item.AuctionHousePrice = 0;
                item.MinimumCalculatedPrice = 0;
                item.CheapestPriceComputed = false;
                item.Profit = 0;
                item.ReturnOnInvestment = 0.0;
            }
            ResetShoppingListData();
        }

        private void ResetShoppingListData()
        {
            m_dictNumItemsToCraft = new Dictionary<string, int>();
            m_dictNumItemsToBuy = new Dictionary<string, int>();
            m_dictPriceTotalsByItem = new Dictionary<string, int>();
            m_nTotalCost = 0;
            m_nTotalRevenue = 0;
            m_nTotalProfit = 0;
            m_dTotalExpectedROI = 0.0;
        }

        private IEnumerable<string> GetComponentsFromRawComponentString(string rawComponentString)
        {
            var componentStrings = rawComponentString.Split(',');
            foreach (var componentString in componentStrings)
            {
                var matches = Regex.Match(componentString, @"([a-zA-Z 1-9]+)\(([1-9]+)\){0,1}");
                if (matches.Groups.Count > 1)
                {
                    var num = int.Parse(matches.Groups[2].Value.ToString());
                    foreach (var i in Enumerable.Range(0, num))
                    {
                        yield return matches.Groups[1].Value;
                    }
                }
                else
                {
                    yield return matches.Groups[1].Value;
                }
            }

        }

        public bool ImportXMLData(string sFileName)
        {
            bool result = true;

            ResetAllData();
            try
            {
                XDocument doc = XDocument.Load(sFileName);

                var gwItems = from element in doc.Descendants("item")
                              let itemName = element.Element("name").Value.ToString()
                              let itemId = element.Element("ID").Value.ToString()
                              let components = GetComponentsFromRawComponentString(element.Element("components").Value.ToString())
                              select m_dictItems[itemName] = new GWItem(itemName, itemId, components);

                gwItems.ToArray();

                m_nItemCount = m_dictItems.Count;
                m_listItemNames.AddRange(m_dictItems.Keys);
            }
            catch
            {
                result = false;
            }

            GWItems = null;
            return result;
        }

        public void ImportGuildWarsTradeData()
        {
            Loading = true;

            Parallel.ForEach(GWItems, item =>
            {
                var request = new GuildWarsTradeRequest(item.ID);
                item.AuctionHousePrice = request.GetPrice();
            });

            CalculateMinimumPrices();
            CalculateProfit();

            AHPricesLoaded = true;

            Loading = false;
        }

        public void ImportGWSpiderData()
        {
            Loading = true;

            Parallel.ForEach(GWItems, item =>
            {
                var request = new GW2SpidyRequest(item.ID);
                item.AuctionHousePrice = request.GetPrice();
            });

            CalculateMinimumPrices();
            CalculateProfit();

            AHPricesLoaded = true;

            Loading = false;
        }

        public void CalculateMinimumPrices()
        {
            foreach (var item in m_dictItems.Values)
            {
                ComputeCheapestPriceHelper(item);
            }
        }

        public void ComputeCheapestPriceHelper(GWItem item)
        {
            if (item.CheapestPriceComputed == true)
            {
                return;
            }
            if (item.GetNumComponents() == 0)
            {
                item.MinimumCalculatedPrice = item.AuctionHousePrice;
                item.AddToShoppingList(item.Name);
                item.CheapestPriceComputed = true;
                return;
            }
            else
            {
                int nTotalPrice = 0;
                for (int i = 0; i < item.GetNumComponents(); i++)
                {
                    string sComponentName = item.GetComponentAtIndex(i);
                    GWItem itemComp = m_dictItems[sComponentName];
                    ComputeCheapestPriceHelper(itemComp);
                    nTotalPrice += itemComp.MinimumCalculatedPrice;
                    int numShoppingListcomps = itemComp.GetNumShoppingList();
                    for (int j = 0; j < itemComp.GetNumShoppingList(); j++)
                    {
                        item.AddToShoppingList(itemComp.GetShoppingListItemAtIndex(j));
                    }
                }
                if (nTotalPrice > item.AuctionHousePrice)
                {
                    nTotalPrice = item.AuctionHousePrice;
                    item.ClearShoppingList();
                    item.AddToShoppingList(item.Name);
                }
                item.MinimumCalculatedPrice = nTotalPrice;
                item.CheapestPriceComputed = true;
                return;
            }
        }

        public void CalculateProfit()
        {
            for (int i = 0; i < m_nItemCount; i++)
            {
                string sName = m_listItemNames[i];
                GWItem item = m_dictItems[sName];
                int nAHPrice = item.AuctionHousePrice;
                int nMinPrice = item.MinimumCalculatedPrice;
                double dAHPrice = (double)nAHPrice;
                dAHPrice *= 0.85;
                nAHPrice = (int)dAHPrice;
                int nProfit = nAHPrice - nMinPrice;
                item.Profit = nProfit;
                item.ReturnOnInvestment = (((double)nAHPrice / (double)nMinPrice) - 1.0);
            }
        }

        public void ReadNumToCraftFromGUI(Dictionary<string, int> dict)
        {
            ResetShoppingListData();

            for (int i = 0; i < m_nItemCount; i++)
            {
                string sName = m_listItemNames[i];
                m_dictNumItemsToCraft[sName] = 0;
            }
            foreach (KeyValuePair<string, int> kvp in dict)
            {
                string sName = kvp.Key;
                m_dictNumItemsToCraft[sName] = kvp.Value;
            }
        }

        public void CalculateShoppingList()
        {
            ResetShoppingListData();

            foreach (var item in m_dictItems.Where(itm => itm.Value.ShoppingAmount > 0))
            {
                for (int i = 0; i < item.Value.ShoppingAmount; i++)
                    foreach (var listItem in item.Value.GetShoppingList())
                    {
                        if (m_dictNumItemsToBuy.ContainsKey(listItem))
                            m_dictNumItemsToBuy[listItem]++;
                        else
                            m_dictNumItemsToBuy[listItem] = 1;
                    }
            }

            ShoppingList = null;
        }

        public void CalculateTotals()
        {
            for (int i = 0; i < m_nItemCount; i++)
            {
                string sName = m_listItemNames[i];
                int nCount = m_dictNumItemsToBuy[sName];
                int nAHPrice = m_dictItems[sName].AuctionHousePrice;
                m_dictPriceTotalsByItem[sName] = nCount * nAHPrice;
            }

            m_nTotalCost = 0;
            m_nTotalRevenue = 0;
            for (int i = 0; i < m_nItemCount; i++)
            {
                string sName = m_listItemNames[i];
                m_nTotalCost += m_dictPriceTotalsByItem[sName];
                m_nTotalRevenue += m_dictNumItemsToCraft[sName] * m_dictItems[sName].AuctionHousePrice;

            }

            double dTotalRevenue = (double)m_nTotalRevenue;
            dTotalRevenue *= 0.85;
            m_nTotalRevenue = (int)dTotalRevenue;
            m_nTotalProfit = m_nTotalRevenue - m_nTotalCost;
            double dTotalCost = (double)m_nTotalCost;
            m_dTotalExpectedROI = (dTotalRevenue / dTotalCost) - 1.0;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        public void NotifyPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion

    }
}
